package com.xgk.boot.framework.common.util.jwt;


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.xgk.boot.framework.common.constant.TokenConstants;
import com.xgk.boot.framework.common.exception.ServerException;
import com.xgk.boot.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xgk.boot.framework.common.exception.util.ServiceExceptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
public class JwtUtil {

    /**
     * 签发者
     */
    private static String ISS = "ELM";

    //TODO:目前密钥采用固定在，后期可以自行配置
    private static String SECRET_KEY;
    private static String DEFAULT_SECRET_KEY="Authorization-elm";

    private static long NEAR_EXPIRATION_THRESHOLD = 30*60*1000; // 30 minute before expiration is considered near expiration

    /**
     * 默认有效期8小时，单位秒
     */
    private static Long EXP_TIME =8*60*60L;


    public static void updateSecretKey(String secretKey){
        SECRET_KEY = secretKey;
    }

    public static void updateNearExpiretionTime(Duration time){
        NEAR_EXPIRATION_THRESHOLD = time.getSeconds()*1000;
    }

    /**
     * 生成token
     * @param claims
     * @return
     */
    public static String generateJwt(Map<String, String> claims) {
        return generateJwt(claims, EXP_TIME);
    }

    /**
     * 生成token
     * @param claims
     * @param expiresIn 有效期，单位ms
     * @return
     */
    public static String generateJwt(Map<String, String> claims, Long expiresIn) {
        Long current = System.currentTimeMillis();
        //颁发日期往前推20秒，防止user的时间早于网关时间造成token解析失败
        Long issuedAt = current - 20000;
        Long expiresAt = current + expiresIn;
        return generateJwt(claims, issuedAt, expiresAt);
    }

    /**
     * 生成token
     * @param claims
     * @param issuedAt  开始时间
     * @param expiresAt 到期时间
     * @return
     */
    public static String generateJwt(Map<String, String> claims, Long issuedAt, Long expiresAt) {
        if(expiresAt-issuedAt > EXP_TIME*1000+1*60*1000){
            throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.TOKEN_PARAMETER_ERROR);
        }
        JWTCreator.Builder builder = JWT.create()
                .withIssuer(ISS)
                .withIssuedAt(new Date(issuedAt))
                .withExpiresAt(new Date(expiresAt));
        claims.put(TokenConstants.TOKEN_CREATE_TIME,String.valueOf(issuedAt));
        claims.put(TokenConstants.TOKEN_EXPRIE_TIME,String.valueOf(expiresAt));
        for (String key : claims.keySet()) {
            builder.withClaim(key, claims.get(key));
        }

        String secretKey = getSecretKey();
        return builder.sign(Algorithm.HMAC256(secretKey));
    }

    public static String renewalJwt(String jwt) {
        Map<String, Claim> stringClaimMap = extractClaims(jwt);
        Map<String, String> map =new HashMap<>();
        Set<Map.Entry<String, Claim>> entries = stringClaimMap.entrySet();
        for (Map.Entry<String, Claim> entry : entries) {
            String key = entry.getKey();
            if(key.equals(TokenConstants.TOKEN_EXP) || key.equals(TokenConstants.TOKEN_IAT)){
                continue;
            }
            Claim value = entry.getValue();
            map.put(key,value.asString());
        }
        String createTime = stringClaimMap.get(TokenConstants.TOKEN_CREATE_TIME).asString();
        String expireTime = stringClaimMap.get(TokenConstants.TOKEN_EXPRIE_TIME).asString();
        Long tokenEffectiveDuration = Long.valueOf(expireTime)-Long.valueOf(createTime)-20000;
        return generateJwt(map,tokenEffectiveDuration);
    }

    public static Map<String, Claim> extractClaims(String jwt) {
        try {
            return extractClaimsWithException(jwt);
        } catch (TokenExpiredException e) {
            //自动续期
            DecodedJWT decodedJWT = JWT.decode(jwt);
            return decodedJWT.getClaims();
        }
    }

    public static Map<String, Claim> extractClaimsWithException(String jwt) {
        String secretKey = getSecretKey();
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secretKey))
                .withIssuer(ISS)
                .build();
        DecodedJWT decodedJWT = jwtVerifier.verify(jwt);
        return decodedJWT.getClaims();
    }

    public static Boolean isExpired(String jwt) {
        try {
            String secretKey = getSecretKey();
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secretKey))
                    .withIssuer(ISS)
                    .build();
            jwtVerifier.verify(jwt);
        } catch (Exception e) {
            return true;
        }
        return false;
    }

    public static boolean isTokenNearExpiration(String token) {
        try {
//            if(!isExpired(token)){
//                return false;
//            }
            Map<String, Claim> stringClaimMap = extractClaims(token);
            Claim creatTimeClaim = stringClaimMap.get(TokenConstants.TOKEN_CREATE_TIME);
            Claim exprieTimeClaim = stringClaimMap.get(TokenConstants.TOKEN_EXPRIE_TIME);
            Long start = Long.valueOf(creatTimeClaim.asString());
            Long expire = Long.valueOf(exprieTimeClaim.asString());
//            if((expire-start)-2*60*1000> EXP_TIME*1000){
//                //有效期超过2小时的token，
//                return false;
//            }
            long timeUntilExpiration = expire - System.currentTimeMillis();
            return Math.abs(timeUntilExpiration) <= NEAR_EXPIRATION_THRESHOLD;
        } catch (Exception e) {
            return false;
        }
    }

    private static String getSecretKey() {
        if (StringUtils.isBlank(SECRET_KEY)) {
           return DEFAULT_SECRET_KEY;
        }
        return SECRET_KEY;
    }

    //license更新，则更新密钥，微服务模式防止user或者webapi单独重启造成key不一致



    public static void main(String[] args) {
        String jwt ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJURSI6IjE3MzA2MTIxMTQyNDUiLCJpc3MiOiJBRk0iLCJUVUkiOiI1NjA4OTI0MDk5MDc3NzM0NDAiLCJleHAiOjE3MzA2MTIxMTQsImlhdCI6MTczMDYxMTk5NCwiVEMiOiIxNzMwNjExOTk0MjQ1In0.yhP4z2_bZcndYUU4GANbRVk-ViwDU46CBHTb4GnUSX0";
//        String jwt ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJURSI6IjE3Mjg2MjIyNDY2NDciLCJpc3MiOiJBRk0iLCJUVUkiOiIwIiwiZXhwIjoxNzI4NjIyMjQ2LCJpYXQiOjE3Mjg2MTUwMjYsIlRDIjoiMTcyODYxNTAyNjY0NyJ9.EiKnXJO8e6AJ4HJxODuJW4zp2RTuFT_PGJ8RvpeR36s";
//        Boolean expired = isExpired(jwt);
        boolean tokenNearExpiration = isTokenNearExpiration(jwt);
        System.out.println(tokenNearExpiration);
        String s = renewalJwt(jwt);
        System.out.println(s);
    }
}